import $ from 'jquery'
import * as joint from 'jointjs'
import Transformer from './cell/transformer'
import Join from './cell/join'
import Link from './cell/link'
import Rect from './cell/rect'
import Input from './cell/input'
import Union from './cell/union'
import Aggregator from './cell/aggregator'
import Transposer from './cell/transposer'
import RowTransposer from './cell/row-transposer'
import Output from './cell/output'
import ElementModel from './model/element-model'
import LinkModel from './model/link-model'
import Slots from './graph/slot'
import LinkView from './graph/link-view'
import RandColor from './graph/rand-color'
import RandName from './graph/rand-name'
import * as cc from './util/constants'
import {
    colorTheme
} from './graph/theme'
import Movement from './graph/movement'

class Graph {
    constructor(...args) {
        this.graph = new joint.dia.Graph()
        this.jointAttrs = $.extend(true, {}, this.getBasicAttrs(), args[0])
        this.el = args[0].el
        this.$element = $(args[0].el)
        this.paper = new joint.dia.Paper(this.jointAttrs)
        this._events = {}

        this.slots = new Slots(this)
        this.linkView = new LinkView()
        this.randColor = new RandColor(this)
        this.randName = new RandName(this)

        this._readOnly = args[0].readOnly === true
        this.compact = args[0].style == 'compact'

        this.setTheme(args[0].theme)
    }

    get readOnly() {
        return this._readOnly
    }

    getBasicAttrs() {
        return {
            model: this.graph,
            gridSize: 1,
            interactive: false
        }
    }

    setTheme(themeName) {
        themeName = themeName || 'black'
        if (colorTheme[themeName]) {
            for (let i in colorTheme) {
                this.$element.removeClass(`joint-theme-${i}`)
            }
            this.$element.addClass(`joint-theme-${themeName}`)
            this.theme = themeName
            this.colorTheme = colorTheme[this.theme]
            let clicked
            for (let i in this.elemView) {
                this.elemView[i].setColor()
                if (this.elemView[i].clicked) {
                    clicked = this.elemView[i]
                }
            }
            clicked && clicked.updateClicked() // 最后更新点击的节点。不然会被覆盖样式
            let linkModels = this.linkView.getModel()
            for (let i in linkModels) {
                let link = this.findLinkView(linkModels[i])
                link && link.updateTheme()
            }
        }
    }

    addCell(cell) {
        cell.parent = this
        this.graph.addCell(cell.jointInstance)
        let cellAttr = cell.jointInstance.attributes
    }

    /**
     * 渲染节点和线
     * @param {object} model 模型
     * @param {function} cb 渲染完成后的回调
     */
    render(model, cb) {
        this.elemView = {}
        this.linkView.clear()
        this.slots.clear()
        this.randColor.clear()
        model = model || {
            nodeList: [],
            nodeRelationList: []
        }
        let elements = model.nodeList.map(e => {
            return new ElementModel(e)
        })
        let links = model.nodeRelationList.map(e => {
            return new LinkModel(e)
        })

        this.randName.initialize(elements)
        // 初始化颜色
        this.randColor.initialize(model.nodeList
            .filter(e => e.nodeType == 'INPUT')
            .map(e => e.style.color))

        // draw element
        $.each(elements, (index, item) => {
            this.renderElement(item)
            if(item.filterVOList&&item.filterVOList.length>0){
                this.elemView[item.id].setFilter(item.filterVOList)
            }
            if(item.inputChangeVO&&item.inputChangeVO!==null){
                this.elemView[item.id].setChangeSign(item.inputChangeVO)
            }
        })

        // FIXME 延迟执行，element可能未渲染完成，导致link锚点位置不对的情况
        setTimeout(() => {
            $.each(links, (index, item) => {
                this.renderLink(this.elemView[item.startId], this.elemView[item.endId])
            })
            if(arguments[2]!==false){
                this.bindEvent()
                this.bindOpenEvent()
            }
            this.resetActionMenu()

            cb && cb()
        }, 500)
    }

    /**
     * 用于渲染无交互的只读的mini图。
     * 只渲染节点，不渲染连线
     * @param {object} model 模型
     * @param {function} cb 渲染完成后的回调
     */
    renderMinimap(model, cb) {
        this.elemView = {}
        this.linkView.clear()
        this.slots.clear()
        this.randColor.clear()
        model = model || {
            nodeList: [],
            nodeRelationList: []
        }
        let elements = model.nodeList.map(e => {
            return new ElementModel(e)
        })

        // this.randName.initialize(elements)
        // // 初始化颜色
        // this.randColor.initialize(model.nodeList
        //     .filter(e => e.nodeType == 'INPUT')
        //     .map(e => e.style.color))

        // draw element
        $.each(elements, (index, item) => {
            this.renderElement(item, true)
        })

        this.miniMap = true

        cb && cb()
    }

    set miniMap(flag) {
        this._miniMap = flag
        this.$element.toggleClass('minimap', flag)
    }

    get miniMap() {
        return this._miniMap
    }

    isDroppedTarget() {
        return this._isDropped
    }

    bindEvent() {
        this.$element.on('dragenter dragleave', e => {
            if ($(e.target).is('svg')) {
                this._isDropped = true // 判断是否drop
                setTimeout(() => {
                    this._isDropped = false
                }, 0)
            }
        })

        this.$element.off('mousedown.flowGraph').on('mousedown.flowGraph', event => {
            for (let i in this.elemView) {
                if (!this.elemView[i].$mask.is($(event.target)) &&
                    $(event.target).closest('.action-toggle').length == 0) {
                    this.elemView[i].clicked = false
                    this.elemView[i].showActionMenu = false
                }
            }
        })

        this.paper.on('link:mouseenter link:mouseover', (a) => {
            let linkView = this.linkView.findViewByJointId(a.model.id)
            if (linkView) {
                linkView.hover = true
            }
        })

        this.paper.on('link:mouseleave link:mouseout', (a) => {
            let linkView = this.linkView.findViewByJointId(a.model.id)
            if (linkView) {
                linkView.hover = false
            }
        })

    }

    /**
     * 绑定对外开放的接口
     */
    bindOpenEvent() {
        this.$element.on('click', e => {
            if (!$(e.target).closest('.element-mask').length &&
                !$(e.target).closest('.element-text').length) {
                if ($(document.activeElement).is('input.input')) {
                    $(document.activeElement).trigger('blur')
                }
                this.unhighlightLinks()
                this.$element.trigger('click_blank', [this])
            }
        })

        // 对外接口
        let openEvents = this.eventTypes()
        for (let type in openEvents) {
            this.$element.on(type, (event, element) => {
                if (this._events[event.type]) {
                    this._events[event.type](event, element)
                }
            })
        }
    }

    eventTypes() {
        return {
            'click_node': 'click_node',
            'dblclick_node': 'dblclick_node',
            'click_blank': 'click_blank',
            'change_color': 'change_color',
            'change_name': 'change_name',
            'execute_node': 'execute_node',
            'remove_node': 'remove_node',
            'create_node': 'create_node',
            'insert_node': 'insert_node',
            'create_link': 'create_link',
            'relink_nodes': 'relink_nodes',
            'move_nodes': 'move_nodes',
            'overflow_error': 'overflow_error',
            'replace_input': 'replace_input',
            'reflow_node': 'reflow_node'
        }
    }

    /**
     * 绑定事件
     * @param {String} eventName 事件名
     * @param {function} callback 回调
     */
    on(eventName, callback) {
        eventName = eventName.trim().split(/\s+/)
        for (let i in eventName) {
            this._events[eventName[i]] = callback
        }
    }

    /**
     * 解绑事件
     * @param {String} eventName 事件名
     */
    off(eventName) {
        eventName = eventName.trim().split(/\s+/)
        for (let i in eventName) {
            if (this._events[eventName[i]]) {
                delete this._events[eventName[i]]
            }
        }
    }

    trigger(eventType, opts) {
        this.$element.trigger(eventType, opts)
    }

    /**
     * 设置当前点中值。传空则所有节点都未点中
     * @param {Element} element 
     * @return 返回点击的element
     */
    setClicked(element) {
        let id = (element && typeof element == 'object') ? element.id : element
        let clickedElem
        for (let i in this.elemView) {
            if (id == this.elemView[i].id) {
                this.elemView[i].clicked = true
                clickedElem = this.elemView[i]
            } else {
                this.elemView[i].clicked = false
            }
        }
        return clickedElem
    }

    renderElement(data, minimapFlag) {
        let extra = {
                minimap: minimapFlag,
                compact: this.compact,
            },
            cell
        switch (data.type) {
            case 'join':
                cell = new Join(data, extra)
                break
            case "transformer":
                cell = new Transformer(data, extra)
                break
            case 'input':
                cell = new Input(data, extra)
                break
            case 'union':
                cell = new Union(data, extra)
                break
            case 'transposer':
                cell = new Transposer(data, extra)
                break
            case 'row-transposer':
                cell = new RowTransposer(data, extra)
                break
            case 'aggregator':
                cell = new Aggregator(data, extra)
                break
            case 'output':
                cell = new Output(data, extra)
                break
            default: // error rect
                cell = new Rect(data, extra)
        }

        cell.addToGraph(this)

        this.elemView[data.id] = cell
        this.slots.fill(cell)
        this.resizeSvg()
        return cell
    }

    renderLink(sourceElem, targetElem, label) {
        let link = new Link(sourceElem, targetElem)
        link.addToGraph(this)
        this.linkView.add(link)
        return link
    }

    removeElement(elem) {
        this._removeElement(elem)
        this.resetActionMenu()
        this.$element.trigger('remove_node', [elem])
    }

    undoCreateElement(elem) {
        elem.undoCreate()
        this._removeElement(elem)
        this.resetActionMenu()
    }
    
    _removeElement(elem) {
        delete this.elemView[elem.id]
        this.linkView.remove(elem.id)
        this.slots.empty(elem.model.x, elem.model.y)
        let moves = this.slots.calculateRemoveSlot(elem)
        this.moveElements(moves.move)
    }
    
    /**
     * 移动元素
     * @param {Array[Movement]}
     * @param boolean triggerEvent 触发移动节点事件。默认true
     * @return 返回移动的节点
     */
    moveElements(elementsToMove, triggerEvent) {
        let movedNodes = []
        this.slots.move(elementsToMove)
        for (let i in elementsToMove) {
            elementsToMove[i].element.move(elementsToMove[i].dx, elementsToMove[i].dy)
            let element = elementsToMove[i].element
            let found = false
            for (let i in movedNodes) {
                if (movedNodes[i] == element) {
                    found = true
                    break
                }
            }
            if (!found) {
                movedNodes.push(element)
            }
        }
        this.resizeSvg()
        if (movedNodes.length > 0 && triggerEvent !== false) { // 有节点移动了才触发事件
            this.trigger('move_nodes', [movedNodes])
        }
        return movedNodes
    }

    get model() {
        let nodeList = []
        for (let i in this.elemView) {
            nodeList.push(this.elemView[i].model)
        }
        let nodeRelationList = this.linkView.getModel()
        return {
            nodeList,
            nodeRelationList
        }
    }

    resizeSvg() {
        if (this.compact) {
            $(this.paper.svg).css({
                width: (this.slots.maxX + 1) * (cc.COMPACT_GRID_WIDTH) + cc.COMPACT_GRAPH_PADDING_TOP + 60,
                height: (this.slots.maxY + 1) * (cc.COMPACT_GRID_HEIGHT) + cc.COMPACT_GRAPH_PADDING_LEFT,
            })
        } else {
            $(this.paper.svg).css({
                width: (this.slots.maxX + 1) * (this.miniMap ? cc.MINI_GRID_WIDTH : cc.GRID_WIDTH) + cc.GRAPH_PADDING_LEFT + 100,
                height: (this.slots.maxY + 1) * (this.miniMap ? cc.MINI_GRID_HEIGHT : cc.GRID_HEIGHT) + cc.GRAPH_PADDING_TOP,
            })
        }
    }

    /**
     * 
     * @param {Element} element 
     */
    set draggingElement(element) {
        this._draggingElement = element
    }

    get draggingElement() {
        return this._draggingElement
    }

    /**
     * 计算一个节点的前置节点
     * @param {Element} element 
     * @param {boolean} recursive 递归查询前置，及前置的前置
     */
    precedingElements(element, recursive) {
        return this.linkView.getStartIdsByEndId(element.id, recursive)
            .map(e => this.elemView[e])
    }

    isPrecedingElement(tester, testee) {
        return this.linkView.getStartIdsByEndId(tester.id, true).indexOf("" + testee.id) > -1
    }

    /**
     * 计算一个节点的后置节点
     * @param {Element} element 
     * @param {boolean} recursive 递归查询后置，及后置的后置
     */
    succeedingElements(element, recursive) {
        return this.linkView.getEndIdsByStartId(element.id, recursive)
            .map(e => this.elemView[e])
    }

    isSucceedingElement(tester, testee) {
        return this.linkView.getEndIdsByStartId(tester.id, true).indexOf("" + testee.id) > -1
    }

    createLink(dropData, dragData, relinkFlag) {
        let a = this.slots.calculateRelinkSlot(this.elemView[dragData.id], this.elemView[dropData.id])
        if (a.error) {
            this.trigger('overflow_error')
            return false
        }
        this.renderLink(this.elemView[dragData.id], this.elemView[dropData.id])
        this.trigger('create_link', [
            [this.elemView[dragData.id], this.elemView[dropData.id]]
        ])
        this.resetActionMenu()
        this.moveElements(a.move)
        return true
    }

    /**
     * Create join/union element
     * @param {ElementModel} dropData data item
     * @param {ElementModel} dragData data item
     * @param {string} type 节点类型
     * @return {Element} 返回创建的element
     */
    createElement(dropData, dragData, type) {
        let newData = $.extend(true, {}, dropData)
        newData.taskArgs = {
            inComingNodes: [dropData.id]
        }
        newData.type = type
        newData.editAble = true
        newData.showName = this.randName.nextName(type)
        let newElement
        newData.id = joint.util.uuid()
        if (dragData) {
            newData.taskArgs.inComingNodes.push(dragData.id)
            let avail = this.slots.calculateNewSlot(dropData.x, dropData.y, dragData.x, dragData.y)
            if (avail.error) {
                this.trigger('overflow_error')
                return
            }
            this.moveElements(avail.move)
            newData = $.extend(newData, avail.add)
            newElement = this.renderElement(newData)
            this.renderLink(this.elemView[dropData.id], this.elemView[newData.id])
            this.renderLink(this.elemView[dragData.id], this.elemView[newData.id])
        } else {
            let avail = this.slots.calculateNewSlot(dropData.x, dropData.y)
            if (avail.error) {
                this.trigger('overflow_error')
                return
            }
            this.moveElements(avail.move)
            newData = $.extend(newData, avail.add)
            newElement = this.renderElement(newData)
            this.renderLink(this.elemView[dropData.id], this.elemView[newData.id])
        }
        this.resetActionMenu()
        this.linkView.refresh()
        this.trigger('create_node', [newElement])
        return newElement
    }

    /**
     * 在原节点后插入新节点，原节点的后续节点都变成新节点的后续
     * @param {ElementModel} srcModel data item
     * @param {string} type 节点类型
     * @return {Element} 返回创建的element
     */
    insertElement(srcModel, type) {
        let newData = $.extend(true, {}, srcModel)
        newData.taskArgs = {
            inComingNodes: [srcModel.id]
        }
        newData.type = type
        newData.editAble = true
        newData.showName = this.randName.nextName(type)
        let newElement
        newData.id = joint.util.uuid()

        let avail = this.slots.calculateInsertSlot(srcModel)
        if (avail.error) {
            this.trigger('overflow_error')
            return
        }
        // 先移动节点
        var movedElements = this.moveElements(avail.move, false)
        // 再添加节点
        newData = $.extend(newData, avail.add)
        newElement = this.renderElement(newData)
        this.renderLink(this.elemView[srcModel.id], this.elemView[newData.id])

        let successors = this.succeedingElements(srcModel, false)
        for (let i in successors) {
            if (successors[i].id != newData.id) { // 重新连接到新节点
                let link = this.linkView.findView(srcModel.id, successors[i].id)
                this.graph.removeCells(link.jointInstance)
                this.linkView.remove(srcModel.id, successors[i].id)
                this.renderLink(this.elemView[newData.id], this.elemView[successors[i].id])
            }
        }

        this.resetActionMenu()
        this.linkView.refresh()
        this.trigger('insert_node', [{
            inserted: newElement,
            moved: movedElements
        }])
        return newElement

    }

    updateElementId(oldId, newId) {
        if (this.elemView[oldId] && !this.elemView[newId]) {
            this.elemView[newId] = this.elemView[oldId]
            this.elemView[newId].id = newId
            delete this.elemView[oldId]

            this.linkView.update(oldId, newId)
        }
    }

    getModel() {
        let nodeList = []
        for (let i in this.elemView) {
            nodeList.push(this.elemView[i].model)
        }
        return {
            nodeList,
            nodeRelationList: this.linkView.getModel()
        }
    }

    get view() {
        return this.elemView
    }

    /**
     * 重置所有节点的action menu状态。非输出尾节点的+号常亮，无错误的输出节点的执行常亮
     */
    resetActionMenu() {
        for (let i in this.elemView) {
            this.elemView[i].showActionMenu = false
        }
    }

    /**
     * 滚动画布到选中的节点
     */
    scrollToClicked() {
        let clicked
        for (let i in this.elemView) {
            if (this.elemView[i].clicked) {
                clicked = this.elemView[i]
                break
            }
        }
        if (!clicked) {
            return
        }
        this.scrollToElement(clicked)
    }

    /**
     * 滚动画布，使指定的节点到可视区域
     */
    scrollToElement(element) {
        let graph = this.$element,
            w = graph.width(),
            h = graph.height(),
            top = graph.scrollTop(),
            left = graph.scrollLeft(),
            pos = element.getClientPosition()

        let gridW = this.compact ? cc.COMPACT_GRID_WIDTH : cc.GRID_WIDTH,
            gridH = this.compact ? cc.COMPACT_GRID_HEIGHT : cc.GRID_HEIGHT,
            paddingL = this.compact ? cc.COMPACT_GRAPH_PADDING_LEFT : cc.GRAPH_PADDING_LEFT,
            paddingT = this.compact ? cc.COMPACT_GRAPH_PADDING_TOP : cc.GRAPH_PADDING_TOP

        if (w > gridW) {
            if (pos.left < left) {
                graph.scrollLeft(pos.left - paddingL);
            } else if (pos.left + pos.width > left + w) {
                graph.scrollLeft(pos.left + pos.width - w + paddingL);
            }
        }

        if (h > gridH) {
            if (pos.top < top) {
                graph.scrollTop(pos.top - paddingT);
            } else if (pos.top + pos.height > top + h) {
                graph.scrollTop(pos.top + pos.height - h + paddingT);
            }
        }
    }

    /**
     * 设置错误节点，及错误信息
     * @param {Array[Object]} nodeErrors 
     */
    setNodeError(nodeErrors) {
        this.nodeErrors = nodeErrors
        for (let i in this.elemView) {
            let hasError = false
            for (let j in nodeErrors) {
                if (nodeErrors[j].nodeId == i) {
                    this.elemView[i].setError(nodeErrors[j].message)
                    hasError = true
                    break
                }
            }
            if (!hasError) {
                this.elemView[i].clearError()
            }
        }
    }

    /**
     * Create input element
     * @param {obj} model 模型对象
     */
    createStartElement(model) {
        const defaultModel = {
            "showName": "输入",
            "editAble": true,
            "taskArgs": {
                "inComingNodes": [],
                "dataSetId": "",
                "dataSetTable": null
            },
            "style": {
                "x": 0,
                "y": 0,
            }
        }
        defaultModel.showName = this.randName.nextName('input')
        let newData = $.extend(true, {}, defaultModel, model)
        newData.type = 'input'
        newData.style.color = this.randColor.nextColor()
        newData.id = joint.util.uuid()
        let avail = this.slots.calculateLeftSlot()
        if (avail.error) {
            this.trigger('overflow_error')
            return
        }
        this.moveElements(avail.move)
        newData = $.extend(newData, avail.add)
        let newElement = this.renderElement(newData)
        this.resetActionMenu()
        this.linkView.refresh()
        this.trigger('create_node', [newElement])
        return newElement
    }

    /**
     * 通过id查找view。传入id字符串或模型对象
     * @param {string | integer | Object} model 
     */
    findElementView(model) {
        let type = typeof model
        if (type === 'string' || type === 'number') {
            return this.elemView[model]
        } else if (type === 'object') {
            return this.elemView[model.id]
        }
    }

    findElementViewByPosition(x, y) {
        for (let i in this.elemView) {
            if (this.elemView[i].x === x && this.elemView[i].y === y) {
                return this.elemView[i]
            }
        }
    }


    /**
     * 查找link的view
     * @param {LinkModel} linkModel 
     */
    findLinkView(linkModel) {
        return this.linkView.findView(linkModel)
    }

    /**
     * 查找连到节点的线
     * @param {string} elementId 节点id
     */
    findLinkViews(elementId) {
        return this.linkView.findViews(elementId)
    }

    /**
     * 高亮指定link
     * @param {Array} linkModels 
     */
    highlightLinks(linkModels) {
        this.unhighlightLinks();
        for (let i in linkModels) {
            let link = this.findLinkView(linkModels[i])
            if (link) {
                link.highlight(linkModels[i].type)
            }
        }
    }

    /**
     * Object{ nodeId: , type: }
     * @param {Array[Object]} nodes 
     */
    highlightLinksByNodes(nodes) {
        let linkModels = []
        for (let i = 0; i < nodes.length - 1; i++) {
            for (let j = i + 1; j < nodes.length; j++) {
                let link = this.findLinkView({
                    startId: nodes[i].nodeId,
                    endId: nodes[j].nodeId
                })
                if (link) {
                    link.model.type = nodes[i].type
                    linkModels.push(link.model)
                }
                link = this.findLinkView({
                    startId: nodes[j].nodeId,
                    endId: nodes[i].nodeId,
                })
                if (link) {
                    link.model.type = nodes[j].type
                    linkModels.push(link.model)
                }
            }
        }
        return this.highlightLinks(linkModels)
    }

    unhighlightLinks() {
        let models = this.linkView.getModel()
        for (let i in models) {
            let link = this.linkView.findView(models[i])
            if (link) {
                link.unhighlight()
            }
        }
    }

    reflow(model) {
        function reflowRmove(elem){
            elem.jointInstance.remove()
            elem.$mask.remove()
            elem.$text.remove()
            delete this.elemView[elem.id]
            this.linkView.remove(elem.id)
            this.slots.empty(elem.model.x, elem.model.y)
            this.slots.calculateRemoveSlot(elem)
            this.resetActionMenu()
        }
        function getSourceNodePosition(ids){
            let temp = [{},{}];
            ids.forEach((id,i)=>{
                let targetNode = (model.nodeList||[]).filter(function(node){
                    return node.id == id;
                })
                temp[i] = (targetNode[0]||{}).style;
            })
            return temp;
        }

        function clearStage(){
            for (var i in this.elemView) {
                reflowRmove.call(this,this.elemView[i])
            }
        }
        clearStage.call(this);
        var nodeGroupByXMap = {};
        var nodeGroupByX = [];
        model.nodeList.forEach((item)=>{
            nodeGroupByXMap[item.style.x] = nodeGroupByXMap[item.style.x]||[];
            nodeGroupByXMap[item.style.x].push(item);
        })

        for(let i in nodeGroupByXMap){
            nodeGroupByX = nodeGroupByX.concat(nodeGroupByXMap[i]);
        }

        nodeGroupByX.forEach((item)=>{
            let d = {};
            if(item.nodeType=='INPUT'){
                d = this.slots.calculateLeftSlot();
            }else if(item.taskArgs.inComingNodes.length>0){
                let pos = getSourceNodePosition(item.taskArgs.inComingNodes);
                if(pos&&!!pos[0]){
                    d = this.slots.calculateNewSlot(pos[0].x,pos[0].y,pos[1].x,pos[1].y);
                }
            }
            if(d.move){
                this.moveElements(d.move,false);
            }
            if(d.add){
                $.extend(item, d.add);
            }
            this.renderElement(new ElementModel(item));
        })
        let tempNodeList = this.getModel().nodeList;
        model.nodeList = tempNodeList;
        clearStage.call(this);
        this.render(model,()=>{
            if(this.nodeErrors&&this.nodeErrors.length>0){
                this.setNodeError(this.nodeErrors)
            }
            this.$element.trigger('reflow_node');
        },false);
    }

    destroy() {
        // unbind events
        let events = this.eventTypes()
        for (let i in events) {
            this.off(events[i])
        }
    }
}

export default Graph